//
//  DGCoreTextView.m
//  DGCoreTextView
//
//  Created by Jinxiao on 13-1-11.
//  Copyright (c) 2013年 debugeek. All rights reserved.
//

#import "DGCoreTextView.h"
#import "DGCoreTextPattern.h"
#import "DGCoreTextPatternSetting.h"

@interface DGCoreTextView ()
@property (readwrite, nonatomic, assign) CGRect drawingRect;
@property (readwrite, nonatomic, assign) CTFrameRef drawingFrame;
@property (readwrite, nonatomic, assign) BOOL innerTouched;
@property (readwrite, nonatomic, strong) DGCoreTextPattern *touchedPattern;
@end

@implementation DGCoreTextView

- (void)dealloc {
    if(self.drawingFrame) {
        CFRelease(self.drawingFrame);
    }
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
    CGContextFillRect(context, rect);

    CGContextSaveGState(context);

    CGContextConcatCTM(context, CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f));

    self.drawingRect = CGRectMake(0.f, 0, self.bounds.size.width, self.bounds.size.height);

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.editor.styledString);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.drawingRect);

    if(self.drawingFrame) {
        CFRelease(self.drawingFrame);
    }

    self.drawingFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
    CGPathRelease(path);
    CFRelease(framesetter);

    CTFrameDraw(self.drawingFrame, context);
    CGContextRestoreGState(context);
}

- (void)setEditor:(DGCoreTextEditor *)editor {
    _editor = editor;

    [self setNeedsDisplay];
}

- (CGSize)sizeThatFits:(CGSize)size {
    return [self.editor sizeThatFits:size];
}

- (CFIndex)stringIndexForPosition:(CGPoint)position inFrame:(CTFrameRef)frame {
    CFIndex lineIndex = -1;
    CFIndex stringIndex = -1;

    NSArray *lines = (NSArray *)CTFrameGetLines(frame);

    CGPoint origins[[lines count]];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);

    CGFloat offsetY = 0.f;

    for(NSInteger idx = 0; idx < lines.count; idx++) {
        CGFloat lineOffsetY = self.frame.size.height - origins[idx].y;

        if(position.y > offsetY && position.y <= lineOffsetY) {
            lineIndex = idx;
            break;
        }

        offsetY = lineOffsetY;
    }

    if(lineIndex >= 0) {
        CTLineRef touchedLine = (__bridge CTLineRef)[lines objectAtIndex:lineIndex];

        CFRange lineRange = CTLineGetStringRange(touchedLine);
        stringIndex = CTLineGetStringIndexForPosition(touchedLine, position);

        if(stringIndex > (lineRange.location + lineRange.length - 1)) {
            stringIndex = -1;
        }
    }

    return stringIndex;
}

- (BOOL)shouldDisplayHighlightedInRange:(NSRange)range {
    __block DGCoreTextPattern *pattern = nil;

    [self.editor.patterns enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if(NSEqualRanges(range, ((DGCoreTextPattern *)obj).range)) {
            pattern = obj;
            *stop = YES;
        }
    }];

    if(!pattern) {
        return NO;
    }

    BOOL result = NO;

    if([self.editor.dataSource respondsToSelector:@selector(coreTextEditor:shouldDisplayHighlightedForPattern:)]) {
        result = [self.editor.dataSource coreTextEditor:self.editor shouldDisplayHighlightedForPattern:pattern];
    } else {
        result = NSEqualRanges(pattern.range, range);
    }

    return result;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CFIndex stringIndex = [self stringIndexForPosition:point inFrame:self.drawingFrame];

    if(stringIndex >= 0) {
        NSRange range;
        NSDictionary *attribution = [self.editor.styledString attributesAtIndex:(NSUInteger)stringIndex effectiveRange:&range];

        if(attribution != nil) {

            DGCoreTextPattern *pattern = attribution[@"pattern"];
            if(pattern.patternSetting.actionBlock != nil) {
                return [super hitTest:point withEvent:event];
            } else {
                return nil;
            }
        }
    }

    return nil;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint touchedPosition = [touch locationInView:self];
    touchedPosition.x -= 5;

    self.innerTouched = NO;

    CFIndex stringIndex = [self stringIndexForPosition:touchedPosition inFrame:self.drawingFrame];

    if(stringIndex >= 0) {
        NSRange range;
        NSDictionary *attribution = [self.editor.styledString attributesAtIndex:(NSUInteger)stringIndex effectiveRange:&range];

        BOOL highlighted = [self shouldDisplayHighlightedInRange:range];

        if(attribution && highlighted) {
            self.innerTouched = YES;

            DGCoreTextPattern *pattern = attribution[@"pattern"];
            self.touchedPattern = pattern;

            [self.editor.styledString setTextColor:pattern.patternSetting.highlightedColor range:pattern.range];
            [self.editor.styledString setTextFont:pattern.patternSetting.highlightedFont range:pattern.range];

            [self setNeedsDisplay];
        }
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if(self.touchedPattern) {
        UITouch *touch = [touches anyObject];
        CGPoint touchedPosition = [touch locationInView:self];
        touchedPosition.x -= 5;

        CFIndex stringIndex = [self stringIndexForPosition:touchedPosition inFrame:self.drawingFrame];

        if(stringIndex >= 0) {
            self.innerTouched = NSLocationInRange(stringIndex, self.touchedPattern.range);

            if(!self.innerTouched) {
                [self.editor.styledString setTextColor:self.touchedPattern.patternSetting.defaultColor range:self.touchedPattern.range];
                [self.editor.styledString setTextFont:self.touchedPattern.patternSetting.defaultFont range:self.touchedPattern.range];
            } else {
                [self.editor.styledString setTextColor:self.touchedPattern.patternSetting.highlightedColor range:self.touchedPattern.range];
                [self.editor.styledString setTextFont:self.touchedPattern.patternSetting.highlightedFont range:self.touchedPattern.range];
            }

            [self setNeedsDisplay];
        }
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(self.touchedPattern) {
        [self.editor.styledString setTextColor:self.touchedPattern.patternSetting.defaultColor range:self.touchedPattern.range];
        [self.editor.styledString setTextFont:self.touchedPattern.patternSetting.defaultFont range:self.touchedPattern.range];

        if(self.innerTouched && self.touchedPattern.patternSetting.actionBlock) {
            self.touchedPattern.patternSetting.actionBlock(self.touchedPattern);
        }

        [self setNeedsDisplay];
    }

    self.innerTouched = NO;
    self.touchedPattern = nil;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    if(self.touchedPattern) {
        [self.editor.styledString setTextColor:self.touchedPattern.patternSetting.defaultColor range:self.touchedPattern.range];
        [self.editor.styledString setTextFont:self.touchedPattern.patternSetting.defaultFont range:self.touchedPattern.range];

        [self setNeedsDisplay];
    }

    self.innerTouched = NO;
    self.touchedPattern = nil;
}

@end
